状态模式
状态模式
定义
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
类图

- 角色说明:
- State(抽象状态角色):抽象类或者接口,定义对象的各种状态和行为。
- ConcreteState(具体状态角色):实现抽象角色类,定义了本状态下的行为,即要做的事情。
- Context(环境角色):定义客户端需要的接口,并且负责具体状态的切换。
案例
- State(抽象状态角色)
public interface PersonState {
void movies();//看电影
void shopping();//逛街
}
- DogState(具体状态角色 1)
public class DogState implements PersonState {
@Override
public void movies() {
System.out.println("[单身狗]一个人偷偷看岛国大片");
}
@Override
public void shopping() {
//单身狗逛条毛街啊
//空实现
System.err.println("[单身狗]单身狗逛条毛街啊");
}
}
- LoveState(具体状态角色 2)
public class LoveState implements PersonState {
@Override
public void movies() {
System.out.println("[恋爱中]一起上电影院看大片~");
}
@Override
public void shopping() {
System.out.println("[恋爱中]一起愉快的逛街去~");
}
}
- 创建环境类,负责状态的切换
public final class Context {
private PersonState mPersonState;
public void setPersonState(PersonState personState) {
mPersonState = personState;
}
public void movies() {
mPersonState.movies();
}
public void shopping() {
mPersonState.shopping();
}
}
- 测试
public final class TestState {
public static void main(String[] args) {
Context context = new Context();
context.setPersonState(new DogState());
context.movies();
context.shopping();
context.setPersonState(new LoveState());
context.movies();
context.shopping();
}
}
结果:
[单身狗]一个人偷偷看岛国大片
[恋爱中]一起上电影院看大片~
[恋爱中]一起愉快的逛街去~
[单身狗]单身狗逛条毛街啊
总结
- 处理对象的各种状态互相转换
- 相比策略模式,策略模式是选择一种策略,不存在状态切换一说,策略一开始就定了;状态模式可以在不同状态对象之间切换。
- 策略模式更符合开闭原则,耦合性更低
状态机
什么是有限状态机?
有限状态机(finite state machine)简称 FSM;状态机就是包含多个状态的数学模型,并可以在状态之间进行变换并且触发一些动作。
一个状态机一般包含以下几个元素:
- State 当前状态
- Event 触发事件
- Transition 状态变换,或者说下一个状态 (次态)
- Action 要执行的动作
状态机实现方式
简单场景:
地铁进站闸口的状态有两个:已经关闭、已经开启两个状态。刷卡后闸口从已关闭变为已开启,人通过后闸口状态从已开启变为已关闭。
| Index | State | Event | NextState | Action |
|---|---|---|---|---|
| 1 | 闸机口 LOCKED | 投币 | 闸机口 UN_LOCKED | 闸机口打开闸门 |
| 2 | 闸机口 LOCKED | 通过 | 闸机口 LOCKED | 闸机口警告 |
| 3 | 闸机口 UN_LOCKED | 投币 | 闸机口 UN _LOCKED | 闸机口退币 |
| 4 | 闸机口 UN_LOCKED | 通过 | 闸机口 LOCKED | 闸机口关闭闸门 |
// T01
Given:一个Locked的进站闸口
When: 投入硬币
Then:打开闸口
// T02
Given:一个Locked的进站闸口
When: 通过闸口
Then:警告提示
// T03
Given:一个Unocked的进站闸口
When: 通过闸口
Then:闸口关闭
// T04
Given:一个Unlocked的进站闸口
When: 投入硬币
Then:退还硬币
// T05
Given:一个闸机口
When: 非法操作
Then:操作失败
基于 If 或 Switch 语句实现的有限状态机
- 保存当前状态
- 状态变更时传入变更事件 event, 先通过 if…else 判断是哪个事件,再对当前事件 event 进行 switch…case 确定要执行的操作
- 进入下一个状态,并执行 action
if(), switch 语句都是 switch 语句,但是 Switch 是一种 Code Bad Smell,因为它本质上一种重复。当代码中有多处相同的 switch 时,会让系统变得晦涩难懂,脆弱,不易修改。
- 定义状态:
public enum EntranceMachineState {
LOCKED, // 闸门锁住
UNLOCKED // 闸门打开
}
- 定义 Event:
public enum Action {
PASS, // 通过闸门
INSERT_COIN // 投币
}
- Transition 状态变换:
public class EntranceMachine {
private EntranceMachineState state;
public EntranceMachine(EntranceMachineState state) {
this.state = state;
}
public String execute(Action action) {
if (Objects.isNull(action)) {
throw new InvalidActionException();
}
if (EntranceMachineState.LOCKED.equals(state)) {
switch (action) {
case INSERT_COIN:
setState(EntranceMachineState.UNLOCKED);
return open();
case PASS:
return alarm();
}
}
if (EntranceMachineState.UNLOCKED.equals(state)) {
switch (action) {
case PASS:
setState(EntranceMachineState.LOCKED);
return close();
case INSERT_COIN:
return refund();
}
}
return null;
}
// action ↓↓↓
private String refund() {
return "refund";
}
private String close() {
return "closed";
}
private String alarm() {
return "alarm";
}
private String open() {
return "opened";
}
}
基于 State 模式实现的有限状态机 (State Design Pattern)
状态模式主要就是对转换规则进行封装,封装状态而暴露行为,状态的改变看起来就是行为发生改变,总结起来,状态模式干了这么几件事
- 需要定义状态抽象类 AbstractState,其中需要包含上下文 Context, 以及所有的抽象事件(event)对象的方法
- 具体的状态需要继承并实现抽象状态
- 上下文 Context 中需要包含所有所需信息,包括所有的状态实例,并指定一个属性指示当前是哪个状态实例
状态模式将与行为与状态相绑定,避免了直接去写大量的 if…else 和 switch…case 编写时可以方便地增加新的状态,并且只需要改变对象的状态就可以改变对象的行为,
- 定义状态接口
/**
* 状态接口,里面定义了各种action
*/
public interface EntranceMachineState {
/**
* 投币
*/
String insertCoin(EntranceMachine entranceMachine);
/**
* 通过闸口
*/
String pass(EntranceMachine entranceMachine);
}
- 状态实现类
/**
* 已经解锁状态
*/
public class LockedEntranceMachineState implements EntranceMachineState {
@Override
public String insertCoin(EntranceMachine entranceMachine) {
return entranceMachine.open();
}
@Override
public String pass(EntranceMachine entranceMachine) {
return entranceMachine.alarm();
}
}
/**
* 未解锁状态
*/
public class UnlockedEntranceMachineState implements EntranceMachineState {
@Override
public String insertCoin(EntranceMachine entranceMachine) {
return entranceMachine.refund();
}
@Override
public String pass(EntranceMachine entranceMachine) {
return entranceMachine.close();
}
}
- 事件动作枚举类
/**
* 动作
*/
public enum Action {
PASS, // 通过闸口
INSERT_COIN // 投币
}
- 状态机类
public class EntranceMachine {
// 定义已有的状态
private EntranceMachineState locked = new LockedEntranceMachineState();
private EntranceMachineState unlocked = new UnlockedEntranceMachineState();
// 当前状态
private EntranceMachineState state;
public EntranceMachine(EntranceMachineState state) {
this.state = state;
}
public String execute(Action action) {
if (Objects.isNull(action)) {
throw new InvalidActionException();
}
if (Action.PASS.equals(action)) {
return state.pass(this);
}
return state.insertCoin(this);
}
public boolean isUnlocked() {
return state == unlocked;
}
public boolean isLocked() {
return state == locked;
}
public String open() {
setState(unlocked);
return "opened";
}
public String alarm() {
setState(locked);
return "alarm";
}
public String refund() {
setState(unlocked);
return "refund";
}
public String close() {
setState(locked);
return "closed";
}
private void setState(EntranceMachineState state) {
this.state = state;
}
}
基于枚举实现的状态机
利用枚举来实现,其主要思想在于可以将状态 State 和事件 Event 都定义成一个枚举类型,并利用枚举也可以定义抽象方法的特性,使得其定义和动作可以写在一起
- 定义状态枚举
public enum StateEnum {
State1 {
@Override
public void doEvent1(StateMachine stateMachine) {
// set next state
// do something else
}
@Override
public void doEvent2(StateMachine stateMachine) {
// set next state
// do something else
}
// ......
},
// .......
;
public abstract void doEvent1(StateMachine stateMachine);
public abstract void doEvent2(StateMachine stateMachine);
// ......
}
- 定义事件枚举
public enum EventEnum {
Event1 {
@Override
public void trigger(StateMachine stateMachine, StateEnum state) {
return state.doEvent1(stateMachine);
}
},
// ......
;
public abstract void trigger(StateMachine stateMachine, StateEnum state);
}
- 组装状态机
public class StateMachine {
private StateEnum state;
public StateMachine(StateEnum state) {
this.state = state;
}
public void execute(EventEnum event) {
event.trigger(this, this.state);
}
}
基于状态集合实现的有限状态机
将状态机每一个状态转换所需要的几个要素都保存下来,在触发对应的事件时,遍历所有的状态映射记录,并根据状态以及上下文信息找到对应的记录,并执行转换得到次态
- 所有的状态机所需数据
public class EntranceMachineTransaction {
public EntranceMachineState currentState;
public Action action;
public EntranceMachineState nextState;
public Event event;
public static EntranceMachineTransaction instance() {
return new EntranceMachineTransaction();
}
}
- 状态机转换
public class EntranceMachine {
static List<EntranceMachineTransaction> entranceMachineTransactionList = new ArrayList<>();
static {
EntranceMachineTransaction instance1 = EntranceMachineTransaction.instance();
instance1.currentState = EntranceMachineState.LOCKED;
instance1.action = Action.INSERT_COIN;
instance1.nextState = EntranceMachineState.LOCKED;
instance1.event = new OpenEvent();
entranceMachineTransactionList.add(instance1);
EntranceMachineTransaction instance2 = EntranceMachineTransaction.instance();
instance2.currentState = EntranceMachineState.UNLOCKED;
instance2.action = Action.PASS;
instance2.nextState = EntranceMachineState.LOCKED;
instance2.event = new AlarmEvent();
entranceMachineTransactionList.add(instance2);
EntranceMachineTransaction instance3 = EntranceMachineTransaction.instance();
instance3.currentState = EntranceMachineState.LOCKED;
instance3.action = Action.PASS;
instance3.nextState = EntranceMachineState.UNLOCKED;
instance3.event = new CloseEvent();
entranceMachineTransactionList.add(instance3);
EntranceMachineTransaction instance4 = EntranceMachineTransaction.instance();
instance4.currentState = EntranceMachineState.UNLOCKED;
instance4.action = Action.INSERT_COIN;
instance4.nextState = EntranceMachineState.UNLOCKED;
instance4.event = new RefundEvent();
entranceMachineTransactionList.add(instance4);
}
private EntranceMachineState state;
public EntranceMachine(EntranceMachineState state) {
setState(state);
}
private void setState(EntranceMachineState state) {
this.state = state;
}
public String execute(Action action) {
Optional<EntranceMachineTransaction> transactionOptional = entranceMachineTransactionList
.stream()
.filter(transaction ->
transaction.action.equals(action) && transaction.currentState.equals(state))
.findFirst();
if (!transactionOptional.isPresent()) {
throw new InvalidActionException();
}
EntranceMachineTransaction transaction = transactionOptional.get();
setState(transaction.nextState);
return transaction.event.execute();
}
}
测试:
public class Test {
public static void main(String[] args) {
EntranceMachineState state = EntranceMachineState.LOCKED;
EntranceMachine stateMachine = new EntranceMachine(state);
String execute = stateMachine.execute(Action.INSERT_COIN);
System.out.println(execute + ",curState=" + stateMachine.getState());
String execute1 = stateMachine.execute(Action.PASS);
System.out.println(execute1 + ",curState=" + stateMachine.getState());
String execute2 = stateMachine.execute(Action.PASS);
System.out.println(execute2 + ",curState=" + stateMachine.getState());
}
}
输出:
opened,curState=UNLOCKED
alarm,curState=LOCKED
closed,curState=UNLOCKED
案例
电梯
我们每天都乘坐电梯,电梯有四种状态:开门、关门、运行、停止。
- 定义电梯状态接口,里面定义电梯的行为
/**
* 电梯状态接口
*
* 定义电梯行为:打开、关闭、运行、停止
*/
public abstract class LiftState {
// 拥有一个电梯对象,用于更新电梯当前状态
protected Lift mLift;
/**
* 通过构造函数引入电梯的实例化对象
*/
public LiftState(Lift lift) {
this.mLift = lift;
}
/**
* 行为:打开电梯门
*/
public abstract void open();
/**
* 行为:关闭电梯门
*/
public abstract void close();
/**
* 行为:电梯运行
*/
public abstract void run();
/**
* 行为:电梯停止运行
*/
public abstract void stop();
}
- 电梯状态实现类
// 电梯处于关闭状态
public class ClosingState extends LiftState {
public ClosingState(Lift lift) {
super(lift);
}
@Override
public void open() {
// 执行开门动作
// 1、变化为开门状态
this.mLift.setState(mLift.getOpenningState());
// 2、开门
this.mLift.open();
}
@Override
public void close() {
System.out.println("执行关门动作");
}
@Override
public void run() {
// 运行动作
// 1、运行状态
this.mLift.setState(mLift.getRunningState());
// 2、运行动作
this.mLift.run();
}
@Override
public void stop() {
// 停止动作
// 1、转化为停止状态
this.mLift.setState(mLift.getStoppingState());
// 2、停止
this.mLift.stop();
}
}
// 打开状态
public class OpeningState extends LiftState {
public OpeningState(Lift lift) {
super(lift);
}
@Override
public void open() {
// 执行开门动作
System.out.println("执行开门动作");
}
@Override
public void close() {
// 执行关门动作
// 1、转化为关门状态
mLift.setState(mLift.getCloseingState());
// 2、关门
mLift.close();
}
@Override
public void run() {
// do noting
// 开门状态,不能执行运行动作
}
@Override
public void stop() {
// do noting
// 开门状态下,不执行停止动作
}
}
// 运行状态
public class RunningState extends LiftState {
public RunningState(Lift lift) {
super(lift);
}
@Override
public void open() {
// do noting
}
@Override
public void close() {
// do noting
}
@Override
public void run() {
// 运行动作
System.out.println("电梯上下运行中...");
}
@Override
public void stop() {
// 停止动作
// 1、转化为停止状态
this.mLift.setState(mLift.getStoppingState());
// 2、停止动作
this.mLift.stop();
}
}
// 停止状态
public class StoppingState extends LiftState {
public StoppingState(Lift lift) {
super(lift);
}
@Override
public void open() {
// 开门动作
// 1、开门状态
this.mLift.setState(mLift.getOpenningState());
// 2、执行开门动作
this.mLift.open();
}
@Override
public void close() {
// do noting
}
@Override
public void run() {
// 运行动作
// 1、运行状态
this.mLift.setState(mLift.getRunningState());
// 2、运行动作
this.mLift.run();
}
@Override
public void stop() {
// 电梯停止动作
System.out.println("电梯停止运行...");
}
}
- 电梯状态机类
/**
* 定义电梯类
*/
public class Lift {
//定义出电梯的所有状态
private LiftState openningState;
private LiftState closingState;
private LiftState runningState;
private LiftState stoppingState;
// 定义当前电梯状态
private LiftState mCurState;
/**
* 构造方法
*/
public Lift() {
openningState = new OpeningState(this);
closingState = new ClosingState(this);
runningState = new RunningState(this);
stoppingState = new StoppingState(this);
}
/**
* 执行开门动作
*/
public void open() {
mCurState.open();
}
/**
* 执行关门动作
*/
public void close() {
mCurState.close();
}
/**
* 执行运行动作
*/
public void run() {
mCurState.run();
}
/**
* 执行停止动作
*/
public void stop() {
mCurState.stop();
}
// ##################设置当前电梯状态#####################
/**
* 设置当前电梯状态
*/
public void setState(LiftState state) {
this.mCurState = state;
}
public LiftState getCurState() {
return mCurState;
}
// ###################获取电梯的全部状态####################
public LiftState getOpenningState() {
return openningState;
}
public LiftState getCloseingState() {
return closingState;
}
public LiftState getRunningState() {
return runningState;
}
public LiftState getStoppingState() {
return stoppingState;
}
}
- 测试
public class Test {
public static void main(String[] args) {
Lift lift = new Lift();
lift.setState(new ClosingState(lift));
System.out.println("当前state=" + lift.getCurState().getClass().getName());
lift.open();
System.out.println("执行open,当前state=" + lift.getCurState().getClass().getName());
lift.close();
System.out.println("执行close,当前state=" + lift.getCurState().getClass().getName());
lift.run();
System.out.println("执行run,当前state=" + lift.getCurState().getClass().getName());
lift.stop();
System.out.println("执行stop,当前state=" + lift.getCurState().getClass().getName());
}
}
输出:
当前state=com.hacket.designpattern.状态机.案例.电梯.state.ClosingState
执行开门动作
执行open,当前state=com.hacket.designpattern.状态机.案例.电梯.state.OpeningState
执行关门动作
执行close,当前state=com.hacket.designpattern.状态机.案例.电梯.state.ClosingState
电梯上下运行中...
执行run,当前state=com.hacket.designpattern.状态机.案例.电梯.state.RunningState
电梯停止运行...
执行stop,当前state=com.hacket.designpattern.状态机.案例.电梯.state.StoppingState
remix 瞄仔游戏状态机实现状态转移
瞄仔游戏状态介绍
游戏分为4个阶段:1下注阶段、2开奖阶段、3等待开始阶段、4游戏结束
1. 下注阶段: 文案提示下注中,喵仔下注倒计时时间:40*1000ms
2. 等待开始阶段:后端开始计算结果,这个阶段轮询后端计算结果,文案提示准备出发 ,喵仔出发前等待时间:3*1000ms
3. 开始阶段:后端结果计算完成,瞄仔开始跑动画,喵仔动画时长:5*1000ms
4. 游戏结束阶段:弹出中奖结果弹窗
不用状态机写法
sealed class Stage(val value: Int, val desc: String = "") {
var gameId: String? = ""
companion object {
fun of(value: Int?): Stage {
return when (value) {
GameBetingStage.value -> GameBetingStage
GameWaitStartStage.value -> GameWaitStartStage
GameStartStage.value -> GameStartStage
GameOver.value -> GameOver
else -> GameUnknown
}
}
fun of(game: KittyGameItem?): Stage {
val stage = when (game?.stage) {
GameBetingStage.value -> GameBetingStage
GameWaitStartStage.value -> GameWaitStartStage
GameStartStage.value -> GameStartStage
GameOver.value -> GameOver
else -> GameUnknown
}
stage.gameId = game?.gameId
return stage
}
}
object GameBetingStage : Stage(1, "下注阶段(1)")
object GameWaitStartStage : Stage(2, "等待开始阶段(2)")
object GameStartStage : Stage(3, "开始阶段(3)")
object GameOver : Stage(4, "游戏结束阶段(4)")
object GameUnknown : Stage(5, "未知阶段(5)")
// 下一个阶段
fun moveToNextStage(): Stage {
return when (this) {
GameBetingStage -> GameWaitStartStage
GameWaitStartStage -> GameStartStage
GameStartStage -> GameOver
GameOver -> GameUnknown
GameUnknown -> GameBetingStage
}
}
}
private suspend fun KittyGameInfo.handleStage() {
return when (Stage.of(game.stage)) {
Stage.GameBetingStage -> { // 下注阶段,更新UI
val totalTimeSeconds = game.betLeftTime() / 1000L
mGameTitleJob?.cancel()
mGameTitleJob = countDownCoroutines(totalTimeSeconds.toInt(), viewModelScope, {}) {
_gameTitleLiveData.value = game.getGoHomeBetStageTitle()
}
delay(game.currentStageLeftTimeMillis(game.stage))
mGameTitleJob?.cancel()
moveToNextStage()
}
Stage.GameWaitStartStage -> { // 等待开始阶段
_gameTitleLiveData.value = getGoHomeTitle()
animateToCrossing()
viewModelScope.launch {
delay(goCrossDuration)
val data = withTimeoutOrNull(game.computeRewardLeftTime() - 100) {
retryGetGameResultSuspend(game.gameId)
}
gameReward = data
if (gameReward == null) {
_gameStatusLiveData.value = false
ToastUtil.Short("服务器异常,请稍后再重试")
gameOver()
}
}
delay(game.currentStageLeftTimeMillis(game.stage))
moveToNextStage()
}
Stage.GameStartStage -> {
animationDuration = game.animDuration()
_gameTitleLiveData.value = getGoHomeTitle()
startAnim()
delay(game.currentStageLeftTimeMillis(game.stage))
moveToNextStage()
}
Stage.GameOver -> {
gameOverStage(from = "正常的游戏流程handleStage")
}
else -> {
LogUtils.e(TAG, "[handleStage]${Stage.of(game)} ")
}
}
}
用状态机写法
- 状态抽象类
abstract class GameState(val scope: CoroutineScope, val game: Game) {
abstract suspend fun doAction()
}
- 各种状态
/**
* 下注阶段(1)
*/
class GameBetingState(scope: CoroutineScope, game: Game) : GameState(scope, game) {
override suspend fun doAction() {
println("下注阶段(1),倒计时更新文案... 等待30秒")
delay(30_000L)
game.setState(game.mGameWaitStartState)
}
}
/**
* 等待开始阶段(2)
*/
class GameWaitStartState(scope: CoroutineScope, game: Game) : GameState(scope, game) {
override suspend fun doAction() {
println("等待开始阶段(2),后端开始计算结果,这个阶段轮询后端计算结果,3秒")
delay(3000L)
game.setState(game.mGameStartState)
}
}
/**
* 开始阶段(3)
*/
class GameStartState(scope: CoroutineScope, game: Game) : GameState(scope, game) {
override suspend fun doAction() {
println("开始阶段(3),后端结果计算完成,瞄仔开始跑动画,5秒")
delay(5_000L)
game.setState(game.mGameOverState)
}
}
/**
* 游戏结束阶段(4)
*/
class GameOverState(scope: CoroutineScope, game: Game) : GameState(scope, game) {
override suspend fun doAction() {
println("游戏结束阶段(4),弹出中奖结果弹窗,开启下一局")
game.setState(game.mGameBetingState)
}
}
- 状态机
class GameMachine(val scope: CoroutineScope) {
val mGameBetingState = GameBetingState(scope, this)
val mGameWaitStartState = GameWaitStartState(scope, this)
val mGameStartState = GameStartState(scope, this)
val mGameOverState = GameOverState(scope, this)
var curState: GameState? = null
suspend fun nextStage() {
curState?.doAction()
}
fun setState(state: GameState) {
curState = state
}
}
- 测试
fun main() = runBlocking {
val game = GameMachine(this)
game.setState(GameBetingState(this, game))
println("开始游戏,当前状态${game.curState?.javaClass?.simpleName}")
while (isActive) {
game.nextStage()
println("下一步,当前状态${game.curState?.javaClass?.simpleName}")
}
}
输出:
开始游戏,当前状态GameBetingState
下注阶段(1),倒计时更新文案... 等待30秒
下一步,当前状态GameWaitStartState
等待开始阶段(2),后端开始计算结果,这个阶段轮询后端计算结果,3秒
下一步,当前状态GameStartState
开始阶段(3),后端结果计算完成,瞄仔开始跑动画,5秒
下一步,当前状态GameOverState
游戏结束阶段(4),弹出中奖结果弹窗,开启下一局
下一步,当前状态GameBetingState
下注阶段(1),倒计时更新文案... 等待30秒
下一步,当前状态GameWaitStartState
等待开始阶段(2),后端开始计算结果,这个阶段轮询后端计算结果,3秒
下一步,当前状态GameStartState
开始阶段(3),后端结果计算完成,瞄仔开始跑动画,5秒
下一步,当前状态GameOverState
游戏结束阶段(4),弹出中奖结果弹窗,开启下一局
下一步,当前状态GameBetingState
下注阶段(1),倒计时更新文案... 等待30秒
下一步,当前状态GameWaitStartState
等待开始阶段(2),后端开始计算结果,这个阶段轮询后端计算结果,3秒
下一步,当前状态GameStartState
开始阶段(3),后端结果计算完成,瞄仔开始跑动画,5秒
下一步,当前状态GameOverState